;;; init-coding.el --- Coding settings -*- lexical-binding: t -*-
;;; Commentary:

;;; Code:

;; 代码缩进风格定义
(use-package editorconfig
  :ensure nil
  :hook (elpaca-after-init . editorconfig-mode)
  :config
  (setq c-basic-offset 4
        c-ts-mode-indent-offset 4
        css-indent-offset 2
        f90-program-indent 3
        f90-associate-indent 3
        f90-critical-indent 3
        haskell-indent-offset 2
        haskell-indentation-layout-offset 2
        haskell-indentation-starter-offset 2
        haskell-indentation-left-offset 2
        haskell-indentation-where-pre-offset 2
        haskell-indentation-where-post-offset 2
        java-ts-mode-indent-offset 4
        python-indent-offset 4
        rust-indent-offset 4
        rust-ts-indent-offset 4
        js-indent-level 2
        typescript-indent-level 2
        typescript-ts-mode-indent-offset 2
        web-mode-attr-indent-offset 2
        web-mode-attr-value-indent-offset 2
        web-mode-code-indent-offset 2
        web-mode-css-indent-offset 2
        web-mode-markup-indent-offset 2
        web-mode-markup-comment-indent-offset 2
        web-mode-sql-indent-offset 2))

(use-package hideshow
  :ensure nil
  :hook (prog-mode . hs-minor-mode)
  :bind (:map prog-mode-map
              ("C-\\" . hs-toggle-hiding)
              ("M-+"  . hs-show-all))
  :custom
  (hs-special-modes-alist
   (mapcar 'purecopy
           '((c-mode "{" "}" "/[*/]" nil nil)
             (c++-mode "{" "}" "/[*/]" nil nil)
             (rust-mode "{" "}" "/[*/]" nil nil)))))

;; 缩进参考线
(use-package hl-indent-scope
  :ensure t
  :hook (prog-mode . hl-indent-scope-mode)
  :config
  ;; Inspired by: github.com/DarthFennec/highlight-indent-guides/blob/master/highlight-indent-guides.el#L904
  (defun hl-indent-scope-auto-set-faces ()
    "Automatically calculate hl-indent-scope face colors.
  Sets odd and even indentation scope face backgrounds based on the current theme."
    (let* ((bk (face-background 'default nil 'default)) ; 获取默认背景色
           (fg (color-name-to-rgb (face-foreground 'default nil 'default))) ; 获取默认前景色
           (bg (color-name-to-rgb bk)) ; 将背景色转换为 RGB
           (oddf 'hl-indent-scope-odd-face)
           (evenf 'hl-indent-scope-even-face)
           (fl (nth 2 (apply 'color-rgb-to-hsl fg))) ; 前景亮度
           (bl (nth 2 (apply 'color-rgb-to-hsl bg))) ; 背景亮度
           mod)
      (if (not (and fg bg))
          (unless highlight-indent-guides-suppress-auto-error
            (message "Error: Cannot determine default face colors"))
        ;; 计算亮度差异并设置颜色
        (setq mod (cond ((< fl bl) -1) ((> fl bl) 1) ((< 0.5 bl) -1) (t 1)))
        (set-face-background oddf (color-lighten-name bk (* mod 5)))      ; 奇数列背景色
        (set-face-background evenf (color-lighten-name bk (* mod 10)))))) ; 偶数列背景色
  (add-hook 'hl-indent-scope-mode-hook #'hl-indent-scope-auto-set-faces)
  (add-hook 'after-load-theme-hook #'hl-indent-scope-auto-set-faces))

;; 基础括号补全
(use-package elec-pair
  :ensure nil
  :hook ((elpaca-after-init . electric-pair-mode)
         (org-mode . (lambda ()
                       (setq-local electric-pair-pairs
                                   (cl-union '((?~ . ?~) (?= . ?=))
                                             electric-pair-pairs
                                             :test #'equal))
                       (setq-local electric-pair-text-pairs
                                   (cl-union '((?~ . ?~) (?= . ?=))
                                             electric-pair-text-pairs
                                             :test #'equal)))))
  :config
  (add-to-list 'electric-pair-pairs '(?` . ?`))
  (add-to-list 'electric-pair-text-pairs '(?` . ?`)))

;; 使用颜色梯度区分括号
(use-package rainbow-delimiters
  :ensure t
  :hook ((prog-mode conf-mode yaml-mode) . rainbow-delimiters-mode)
  :custom
  (rainbow-delimiters-max-face-count 5))

;; 高亮当前括号
(use-package paren
  :ensure nil
  :custom-face
  (show-paren-match ((t (:weight bold))))
  :config
  (setq show-paren-when-point-inside-paren t ; 光标位于括号内部边缘时高亮匹配
        show-paren-when-point-in-periphery t ; 光标位于括号外部边缘时高亮匹配
        blink-matching-paren-highlight-offscreen t ; 左括号超出屏幕外时高亮
        show-paren-context-when-offscreen 'overlay ; 左括号在屏幕之外时显示匹配
        show-paren-delay 0.2))                     ; 高亮延时

;; keeps your code always indented
(use-package aggressive-indent
  :ensure t
  :hook ((emacs-lisp-mode lisp-mode lisp-interaction-mode scheme-mode). aggressive-indent-mode))

(use-package hungry-delete
  :ensure t
  :hook ((prog-mode . hungry-delete-mode)
         (emacs-lisp-mode . (lambda () (setq-local hungry-delete-chars-to-skip " \t\n\r\f\v"))))
  :custom
  (hungry-delete-chars-to-skip " \t\r\f\v"))

;; 智能注释
(use-package smart-comment :defer 2 :ensure t)

;; 格式化代码
(use-package format-all
  :ensure t
  :commands (format-all-region-or-buffer)
  :custom
  (format-all-show-errors 'warnings)
  :config
  (define-format-all-formatter sql-formatter
    (:executable "sql-formatter")
    (:install "npm install -g sql-formatter")
    (:languages "SQL")
    (:features)
    (:format (format-all--buffer-easy executable)))
  
  (setq-default format-all-formatters
                '(("C" (clang-format "--style={IndentWidth: 4, AlignEscapedNewlines: Left, BreakBeforeBraces: Custom, BraceWrapping: {BeforeElse: true}}"))
                  ("C++" (clang-format "--style={IndentWidth: 4, AlignEscapedNewlines: Left, BreakBeforeBraces: Custom, BraceWrapping: {BeforeElse: true}}"))
                  ("CSS"  prettier)
                  ("Fortran" fprettify)
                  ("Haskell" stylish-haskell)
                  ("HTML" prettier)
                  ("JSON" prettier)
                  ("Java" (clang-format "--style={ColumnLimit: 120, IndentWidth: 4, Language: Java, BreakBeforeBraces: Custom, BraceWrapping: {BeforeElse: true}}"))
                  ("JavaScript" prettier)
                  ("Python" (black "--line-length" "120"))
                  ("Shell" (shfmt "-i" "4" "-ci"))
                  ("SQL" (sql-formatter "--language" "plsql"))
                  ("TypeScript" prettier)
                  ("Vue" prettier)
                  ("XML" html-tidy))))

;; 预览 buffer 内的颜色名称或十六进制颜色
(use-package colorful-mode
  :ensure t
  :hook ((help-mode html-mode css-mode) . colorful-mode)
  :custom
  (colorful-use-prefix t))

;; Highlight symbols
;; From: github.com/seagle0128/.emacs.d/blob/master/lisp/init-highlight.el
(use-package symbol-overlay
  
  :ensure (:host github :repo "wolray/symbol-overlay")
  :hook (((prog-mode yaml-mode) . symbol-overlay-mode)
         (iedit-mode            . +symbol-overlay/turn-off-symbol-overlay)
         (iedit-mode-end        . +symbol-overlay/turn-on-symbol-overlay))
  :bind (:map symbol-overlay-mode-map
              ("M-i"  . #'symbol-overlay-put)
              ("M-N"  . #'symbol-overlay-jump-next)
              ("M-P"  . #'symbol-overlay-jump-prev)
              ("<f5>" . #'symbol-overlay-remove-all))
  :custom-face
  (symbol-overlay-default-face ((t (:background "lavender" :foreground "black"))))
  :custom
  (symbol-overlay-idle-time 0.3)
  :config
  (with-no-warnings
    ;; Disable symbol highlighting while selecting
    (defun +symbol-overlay/turn-off-symbol-overlay (&rest _)
      "Turn off symbol highlighting."
      (interactive)
      (symbol-overlay-mode -1))
    (advice-add #'set-mark :after #'+symbol-overlay/turn-off-symbol-overlay)

    (defun +symbol-overlay/turn-on-symbol-overlay (&rest _)
      "Turn on symbol highlighting."
      (interactive)
      (when (derived-mode-p 'prog-mode 'yaml-mode)
        (symbol-overlay-mode 1)))
    (advice-add #'deactivate-mark :after #'+symbol-overlay/turn-on-symbol-overlay)))

;; 快速运行代码
(use-package quickrun
  :ensure t
  :commands (quickrun quickrun-shell))

;; lisp 系集成环境，包含 REPL、代码补全等
(use-package geiser
  :ensure t
  :hook (scheme-mode . geiser-mode)
  :custom
  (geiser-repl-query-on-kill-p nil))

;; geiser 的方言支持
(use-package geiser-chez
  :after (geiser)
  :ensure t
  :init
  (setq scheme-program-name "scheme")) ; use chez scheme

;; Eldoc
(use-package eldoc
  :ensure nil
  :hook (scheme-mode . eldoc-mode))

;; capf 补全前端
(use-package corfu
  :ensure t
  :hook (scheme-mode . corfu-mode)
  :custom
  (corfu-auto t) ; Enable auto completion
  (corfu-auto-prefix 2)
  (corfu-cycle t)                ; Enable cycling for `corfu-next/previous'
  (corfu-quit-at-boundary nil)   ; Never quit at completion boundary
  (corfu-on-exact-match nil))    ; Configure handling of exact matches

(use-package flymake
  :ensure nil
  :hook ((prog-mode . flymake-mode)
         (emacs-lisp-mode . (lambda () (flymake-mode -1))))
  :custom
  (flymake-start-on-flymake-mode t)
  (flymake-no-changes-timeout nil)
  (flymake-start-on-save-buffer t)
  :bind(:map flymake-mode-map
             ("M-p" . #'flymake-goto-prev-error)
             ("M-n" . #'flymake-goto-next-error)))

;; snippet support
(use-package yasnippet
  :ensure t
  :hook (elpaca-after-init . yas-global-mode)
  :config
  ;; From: emacs-china.org/t/yasnippet-mode-snippets-mode/25226/3?u=harl
  ;; 在 *-ts-mode 下加载 *-mode 的 snippets
  (advice-add 'yas--modes-to-activate :around
              (defun yas--get-snippet-tables@reverse-ts-mode (orig-fn &optional mode)
                (funcall orig-fn
                         (or (car (rassq (or mode major-mode) major-mode-remap-alist))
                             mode))))

  ;; 在 *-ts-mode 下保存 snippets 到 *-mode 目录 （也可以试试直接在 yas--table-get-create 上 advice 看看有没有不良影响）
  (advice-add 'yas-new-snippet :around
              (defun yas--new-snippet@reverse-ts-mode (&rest args)
                (cl-letf* ((yas--orig-table-get-create (symbol-function 'yas--table-get-create))
                           ((symbol-function 'yas--table-get-create)
                            (lambda (mode)
                              (funcall yas--orig-table-get-create
                                       (or (car (rassq (or mode major-mode) major-mode-remap-alist))
                                           mode)))))
                  (apply args)))))

;; 多语言代码片段合集
(use-package yasnippet-snippets :after yasnippet :ensure t)

(use-package lsp-bridge
  :ensure (:host github :repo "manateelazycat/lsp-bridge"
                 :files (:defaults "*.el" "*.py" "acm" "core" "langserver" "multiserver" "resources")
                 :build (:not elpaca--byte-compile))
  :bind ((:map lsp-bridge-mode-map
               ("<f6>" . lsp-bridge-popup-complete-menu)
               ("C-}"  . lsp-bridge-popup-documentation-scroll-up)
               ("C-{"  . lsp-bridge-popup-documentation-scroll-down))
         (:map acm-mode-map
               ("C-y" . acm-complete)
               ("M-<" . acm-select-first)
               ("M->" . acm-select-last)
               ("<tab>" . nil)
               ("TAB" . nil)
               ("M-n" . nil)
               ("M-p" . nil)
               ("M-," . nil)
               ("M-." . nil)))
  :init
  ;; 获取 windows 下 python 路径，避免调用 msys2 内的 python
  (when (eq system-type 'windows-nt)
    (setq lsp-bridge-python-command
          (string-trim (shell-command-to-string "python -c \"import sys; print(sys.executable)\""))))

  ;; 开启 lsp-bridge-mode
  (global-lsp-bridge-mode)
  :hook ((elpaca-after-init . (lambda ()
                                (global-eldoc-mode -1)
                                (lsp-bridge-mode))) ; 提前启动 python 进程
         (org-mode . lsp-bridge-toggle-sdcv-helper))
  :custom-face
  (lsp-bridge-peek-border-face ((t (:extend t :background unspecified :height unspecified :strike-through t))))
  :custom
  (lsp-bridge-enable-mode-line nil)     ; 不在 mode-line 显示
  (lsp-bridge-signature-show-function
   'lsp-bridge-signature-show-with-frame)  ; 使用 frame 显示函数签名信息
  (lsp-bridge-signature-show-with-frame-position "point")
  (lsp-bridge-enable-completion-in-string nil)
  (lsp-bridge-enable-auto-format-code nil) ; 自动格式化关闭
  (lsp-bridge-enable-inlay-hint t)         ; 类型嵌入提示
  (lsp-bridge-enable-log nil)              ; 关闭 log
  (lsp-bridge-enable-debug nil)            ; 关闭 debug
  (acm-enable-doc nil)                     ; 关闭文档
  (acm-enable-preview t)                   ; 选择即补全
  (acm-enable-yas nil)                     ; 关闭 yasnippet 模板补全
  (acm-enable-tabnine nil)                 ; 关闭 tabnine 补全
  (acm-enable-codeium nil)                 ; 关闭 codeium 补全
  (acm-enable-copilot nil)                 ; 关闭 copilot 补全
  (acm-enable-capf t)                      ; 针对非 lsp 提供补全
  (acm-enable-quick-access t)              ; 补全项后显示数字索引，通过 alt+n 实现选择

  ;; LSP 的自定义 JSON 配置文件所在位置
  (lsp-bridge-user-langserver-dir (expand-file-name "langserver" no-littering-etc-directory))
  (lsp-bridge-user-multiserver-dir (expand-file-name "multiserver" no-littering-etc-directory))

  ;; 指定特定的 lsp-server
  (lsp-bridge-c-lsp-server "ccls")
  (lsp-bridge-python-lsp-server "pyright")

  ;; 触发补全的最小字符数
  (acm-backend-lsp-candidate-min-length 2)
  (acm-backend-elisp-candidate-min-length 2)
  (acm-backend-yas-candidate-min-length 2)
  (acm-backend-codeium-candidate-min-length 2)
  (acm-backend-search-file-words-candidate-min-length 2)
  :config
  (setq lsp-bridge-jdtls-default-file
        (expand-file-name
         (if (memq system-type '(cygwin windows-nt ms-dos))
             "langserver/jdtls_windows.json"
           "langserver/jdtls.json")
         no-littering-etc-directory))

  ;; 定义语言模式与对应的 LSP 服务器、缩进变量
  (dolist (cfg '((sql-mode         "sqls"    c-basic-offset)
                 (haskell-ts-mode  "hls"     haskell-indent-offset)
                 (f90-mode         "fortls"  f90-program-indent)))
    (let ((mode   (nth 0 cfg))
          (server (nth 1 cfg))
          (indent (nth 2 cfg)))
      (add-to-list 'lsp-bridge-single-lang-server-mode-list (cons mode server))
      (add-to-list 'lsp-bridge-formatting-indent-alist (cons mode indent))))

  ;; 根据文件的扩展名来返回多服务器配置名
  (add-to-list 'lsp-bridge-multi-lang-server-extension-list '(("html") . "html_tailwindcss"))
  (add-to-list 'lsp-bridge-multi-lang-server-extension-list '(("css") . "css_tailwindcss")))

(use-package flymake-bridge
  :after (flymake lsp-bridge)
  :ensure (:host github :repo "liuyinz/flymake-bridge")
  :hook (lsp-bridge-mode . flymake-bridge-setup))

(provide 'init-coding)
;;; init-coding.el ends here
